package com.zhang.img;
 
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
 
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServlet;
 
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
 
/**
 * like12 find bug,20230419
 * 注意：此sun工具类有bug：处理大图片如20MB时会疯狂占用内存 可能导致内存溢出，无限制时可占用高达数G的内存
 * 启动后内存疯狂飙升至2G多才处理成功 1024M直接死掉起不来 正常运行内容占用应为900MB左右
 * like12 modified,20230419,优化 合并一次多余的读文件操作 减少一次多余的缩放操作 绘图时直接缩放
 * 解决大图片如20M时 疯狂占用内存数G的问题
 */
public class CompressImgUtil extends HttpServlet {
	private static final long serialVersionUID = 1L;
 
	/**
	 * 采用指定宽度、高度或压缩比例 的方式对图片进行压缩
	 * like12 modified,20220615,支持指定宽/高(高/宽自适应-使图片不失真)
	 *
	 * @param imgsrc 源图片地址
	 * @param out 目标图片地址
	 * @param widthdist 压缩后图片宽度（当rate==null时，必传）
	 * @param heightdist 压缩后图片高度（当rate==null时，必传）
	 * @param rate 压缩比例
	 */
	public static void compressImg(String imgsrc, ByteArrayOutputStream out, int widthdist,
								   int heightdist, Float rate) {
		try {
			// 1.打开并读取文件
			File srcfile = new File(imgsrc);
			if (!srcfile.exists()) {// 检查文件是否存在
				return;
			}
			// 读取文件
			Image src = ImageIO.read(srcfile);// 这一步对大图片来说如20M 非常耗内存约1G
 
			// 2.计算压缩后的宽高
			// like12 modified,20230419,优化 合并读文件 只读一次,解决2次读白耗内存的问题
			int wideth = src.getWidth(null); // 得到源图宽
			int height = src.getHeight(null); // 得到源图长
			if (wideth == 0 || height == 0 || wideth == -1 || height == -1) {
				return;
			}
			// 如果rate不为空说明是按比例压缩
			if (rate != null && rate > 0) {
				widthdist = (int) (wideth * rate);
				heightdist = (int) (height * rate);
			}
			//like12 modified,20220615,支持指定宽/高(高/宽自适应-使图片不失真)
			else if(rate == null && widthdist > 0 && heightdist == 0){
				rate = (float)(widthdist * 1.0 / wideth);
				heightdist = (int) (height * rate);
			}else if(rate == null && widthdist == 0 && heightdist > 0){
				rate = (float)(heightdist * 1.0 / height);
				widthdist = (int) (wideth * rate);
			}
 
			// 3.开启压缩
			// 准备buff
			BufferedImage bufferedImage = new BufferedImage(widthdist, heightdist, BufferedImage.TYPE_INT_RGB);
			// 开始压缩
			//like12 find bug,20230419,多执行了一次缩放操作，大图片时如20M 多耗费内存数百M
			//bufferedImage.getGraphics().drawImage(
			//		src.getScaledInstance(widthdist, heightdist, Image.SCALE_SMOOTH),// 这是缩放操作
			//		0, 0, null);
			//like12 modified,20230419,优化 绘图时直接缩放
			bufferedImage.getGraphics().drawImage(src, 0, 0, widthdist, heightdist, null);
 
			// 4.写文件
			JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
			encoder.encode(bufferedImage);
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
 
	/**
	 * like12 modified,20230419,优化 合并一次多余的读文件操作 减少一次多余的缩放操作 绘图时直接缩放
	 * 解决大图片如20M时 疯狂占用内存数G的问题
	 *
	 * 采用指定宽度、高度或压缩比例 的方式对图片进行压缩
	 * like12 modified,20220615,支持指定宽/高(高/宽自适应-使图片不失真)
	 *
	 * @param imgsrc 源图片地址
	 * @param imgdist 目标图片地址
	 * @param widthdist 压缩后图片宽度（当rate==null时，必传）
	 * @param heightdist 压缩后图片高度（当rate==null时，必传）
	 * @param rate 压缩比例
	 */
	public static void compressImg(String imgsrc, String imgdist, int widthdist,
			int heightdist, Float rate) {
		try {
			// 1.打开并读取文件
			File srcfile = new File(imgsrc);
			if (!srcfile.exists()) {// 检查文件是否存在
				return;
			}
			// 读取文件
			Image src = ImageIO.read(srcfile);// 这一步对大图片来说如20M 非常耗内存约1G
 
			// 2.计算压缩后的宽高
			// like12 modified,20230419,优化 合并读文件 只读一次,解决2次读白耗内存的问题
			int wideth = src.getWidth(null); // 得到源图宽
			int height = src.getHeight(null); // 得到源图长
			if (wideth == 0 || height == 0 || wideth == -1 || height == -1) {
				return;
			}
			// 如果rate不为空说明是按比例压缩
			if (rate != null && rate > 0) {
				widthdist = (int) (wideth * rate);
				heightdist = (int) (height * rate);
			}
			//like12 modified,20220615,支持指定宽/高(高/宽自适应-使图片不失真)
			else if(rate == null && widthdist > 0 && heightdist == 0){
				rate = (float)(widthdist * 1.0 / wideth);
				heightdist = (int) (height * rate);
			}else if(rate == null && widthdist == 0 && heightdist > 0){
				rate = (float)(heightdist * 1.0 / height);
				widthdist = (int) (wideth * rate);
			}
 
			// 3.开启压缩
			// 准备buff
			BufferedImage bufferedImage = new BufferedImage(widthdist, heightdist, BufferedImage.TYPE_INT_RGB);
			// 开始压缩
			//like12 find bug,20230419,多执行了一次缩放操作，大图片时如20M 多耗费内存数百M
			//bufferedImage.getGraphics().drawImage(
			//		src.getScaledInstance(widthdist, heightdist, Image.SCALE_SMOOTH),// 这是缩放操作
			//		0, 0, null);
			//like12 modified,20230419,优化 绘图时直接缩放
			bufferedImage.getGraphics().drawImage(src, 0, 0, widthdist, heightdist, null);
 
			// 4.写文件
			FileOutputStream out = new FileOutputStream(imgdist);
			JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
			encoder.encode(bufferedImage);
			out.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
	}
 
	/**
	 * 未用的方法(Toolkit) 前面的方法优化后占用内存比本方法还小些
	 * @param imgsrc
	 * @param imgdist
	 * @param widthdist
	 * @param heightdist
	 * @param rate
	 */
	private static void compressImgToolkit(String imgsrc, String imgdist, int widthdist,
										   int heightdist, Float rate) {
		try {
			// 1.打开文件
			File srcfile = new File(imgsrc);
			// 检查文件是否存在
			if (!srcfile.exists()) {
				return;
			}
 
			// 2.使用Toolkit.getImage异步读取源图片 并得到源图片宽高
			Toolkit toolkit = Toolkit.getDefaultToolkit();
			Image srcImage = toolkit.getImage(srcfile.getAbsolutePath()); // 构造Image对象
			int wideth = -1;
			int height = -1;
			boolean flag = true;
			while (flag) {
				wideth = srcImage.getWidth(null); // 得到源图宽
				height = srcImage.getHeight(null); // 得到源图长
				System.out.println("wideth:" + wideth + " height:" + height);
				if (wideth > 0 && height > 0) { // 因为 Toolkit.getImage 是异步读取，如果wideth 和 height 都大于0，表明图片已经加载完毕
					// imageCanvas.setImage(srcImage);
					flag = false;
				} else {
					try {
						Thread.sleep(10);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
 
			// 3.计算压缩后的宽高
			if (wideth == 0 || height == 0 || wideth == -1 || height == -1) {
				return;
			}
			// 如果rate不为空说明是按比例压缩
			if (rate != null && rate > 0) {
				widthdist = (int) (wideth * rate);
				heightdist = (int) (height * rate);
			}
			//like12 modified,20220615,支持指定宽/高(高/宽自适应-使图片不失真)
			else if(rate == null && widthdist > 0 && heightdist == 0){
				rate = (float)(widthdist * 1.0 / wideth);
				heightdist = (int) (height * rate);
			}
			else if(rate == null && widthdist == 0 && heightdist > 0){
				rate = (float)(heightdist * 1.0 / height);
				widthdist = (int) (wideth * rate);
			}
			System.out.println(imgsrc + " " + imgdist + " -- " + widthdist + " " + heightdist);
 
			// 准备buff
			BufferedImage bufferedImage = new BufferedImage(widthdist, heightdist, BufferedImage.TYPE_INT_RGB);
			// 开始压缩
			boolean flag2 = false;
			while (!(flag2 = bufferedImage.getGraphics().drawImage(srcImage, 0,
					0, widthdist, heightdist, null))) {// 绘制缩小后的图
				try {
					Thread.sleep(10);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
 
			try {
				// 写文件
				File outputFile = new File(imgdist);
				if (!outputFile.exists()) {
					outputFile.createNewFile();
				}
 
				FileOutputStream out = new FileOutputStream(outputFile); // 输出到文件流
				JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
				encoder.encode(bufferedImage); // 近JPEG编码
				out.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
 
	/**
	 * 已停用(减少一次读操作 解决大图片如20M时 疯狂占用内存的问题)
	 *
	 * like12 find bug,大图片如20M时有bug,读取图片非常耗内存20M约耗用1G左右
	 * 需优化,应取消这里的读文件,减少一次读操作即可减少一半的内存消耗,此大对象是直接进入老年代,且长久不会回收
	 * 获取图片宽度
	 *
	 * @param file 图片文件
	 * @return 宽度
	 */
	/*public static int[] getImgWidth(File file) {
		InputStream is = null;
		BufferedImage src = null;
		int result[] = { 0, 0 };
		try {
			is = new FileInputStream(file);
			src = ImageIO.read(is);// 这一步对大图片来说如20M 非常耗内存约1G
			result[0] = src.getWidth(null); // 得到源图宽
			result[1] = src.getHeight(null); // 得到源图高
			is.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return result;
	}*/
 
	/**
	 * 获取图片大小 及 尺寸(测试)
	 * @param filePath
	 * @return
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public static Float getImgWidthTest(String filePath) throws FileNotFoundException, IOException {
		//图片大小(文件大小)
		File picture = new File(filePath);
		Float size = Float.parseFloat(String.format("%.1f",
						picture.length() / 1024.0 / 1024.0));
		System.out.println("图片大小:" + size + "MB");
		
		//图片尺寸
		BufferedImage sourceImg  = ImageIO.read(new FileInputStream(picture));
		System.out.println("宽:" + sourceImg.getWidth());
		System.out.println("高:" + sourceImg.getHeight());
 
		return size;
	}
 
	public static void main(String[] args) throws FileNotFoundException, IOException {
		System.out.println("图片压缩测试...");
 
		/*//获取图片大小
		CompressImgUtil.getImgWidthTest("D:/test.jpg");*/
 
		//图片压缩
		String srcFile = "D://test.jpg";
		String destFile = "D://tempCompressImg.jpg";
		//String destFile = "D://test.jpg";//相同路径可以覆盖
		
		File srcfile = new File(srcFile);
		System.out.println("压缩前srcfile size:" + srcfile.length());
		
		//压缩(相同路径可以覆盖)
		//CompressImgUtil.compressImg(srcFile, destFile, 600, 800, null);
		//CompressImgUtil.compressImg(srcFile, destFile, 110, 130, null);
		//CompressImgUtil.compressImg(srcFile, destFile, 110, 0, null);
		//CompressImgUtil.compressImg(srcFile, destFile, 0, 130, null);
		CompressImgUtil.compressImg(srcFile, destFile, 0, 0, Float.parseFloat("0.5"));
 
		//未用的方法(Toolkit) 前面的方法优化后占用内存比本方法还小些
		//CompressImgUtil.compressImgToolkit(srcFile, destFile, 0, 0, Float.parseFloat("0.5"));
		
		File distfile = new File(destFile);
		System.out.println("压缩后distfile size:" + distfile.length());
		
		System.out.println("Finish!");
 
		/*//调试时不退出
		while (true){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}*/
	}
}